home *** CD-ROM | disk | FTP | other *** search
/ User's Choice Windows CD / User's Choice Windows CD (CMS Software)(1993).iso / win_u_z / wt_jan92.zip / SHAW.ZIP / COLUMN.TXT next >
Text File  |  1991-12-23  |  29KB  |  630 lines

  1. THE VIRTUAL COLUMN
  2. BY RICHARD HALE SHAW
  3.  
  4. GETTING A HANDLE ON DDE
  5.  
  6. Windows 3.1 includes DDEML.DLL, a set of routines for creating 
  7. and managing DDE conversations. This column explores the library 
  8. and presents C++ objects that make DDE simpler and easier than 
  9. ever.
  10.  
  11. This column explores the use of C++ as a Windows development 
  12. language. It does so in two ways: first, C++ as *the* 
  13. development language for Windows, largely usurping the role of 
  14. C; and second, C++ as an object-oriented solution to a 
  15. non-object-oriented environment. Let me explain.
  16.  
  17. First, I believe that C will no longer be the dominant 
  18. programming language for Windows developers. I think a number of 
  19. developers will find that they can solve their Windows 
  20. programming problems with Visual Basic, Turbo Pascal for Windows 
  21. or environments like Actor, ToolBook or SmallTalk. But I think 
  22. that most Windows programmers who currently use C will move to 
  23. C++ over the next 18 months.
  24.  
  25. Second, Windows -- by any classical definition of OOP -- is 
  26. *not* an OOP environment. Sure: you can use a window to tie  data
  27. and code (a window procedure) together, and there's simple 
  28. (primitive?) sub-classing. But data encapsulation is relatively 
  29. poor compared to other OOP solutions like C++. Plus, when it 
  30. comes to inheritance and polymorphism, forget it: Windows 
  31. doesn't even come close to offering capabilities like these. 
  32. C++, however, offers these OOP features along with the power of 
  33. C, and, I believe, is the best tool for creating new Windows 
  34. programs.
  35.  
  36. Consequently, this column will explore Windows programming from 
  37. a C++ perspective, and we'll use C++ to create unique solutions 
  38. to Windows programming problems. As we proceed, don't hesitate 
  39. to send me your thoughts on these issues or on specific ideas 
  40. that the column addresses; I'll be sure to share them with 
  41. everyone as space permits.
  42.  
  43. With this in mind, let's look at the first column: on Windows 
  44. DDE.
  45.  
  46. If you've ever written a program that uses Windows' Dynamic  Data
  47. Exchange (DDE), or even attempted to, you know how trying  and
  48. frustrating it is. DDE -- or as my friend and colleague  Charles
  49. Petzold calls it, "the protocol from Hell" -- is one of  the more
  50. exasperating facilities in Windows. The description of  DDE in
  51. the Windows SDK leaves more to the imagination than to  fact, and
  52. the only way to be sure your DDE program worked 
  53. properly has been to test it against Microsoft products that 
  54. implemented it (after all, we all figure that those guys will 
  55. get it right). Unfortunately, this isn't always the case, 
  56. either. But many of us have tried, if desparately, to 
  57. implement DDE correctly, because the benefit to an 
  58. application are so worthwhile.
  59.  
  60. Fortunately, there's hope. Windows 3.1 now includes DDEML.DLL: a 
  61. DLL that packages a number of routines for creating and managing 
  62. DDE conversations. This DDL makes DDE infinitely simpler to 
  63. implement in your programs, and reduces the possibility of  do so
  64. incorrectly. Plus, you can even use DDEML.DLL under 
  65. Windows 3.0.
  66.  
  67. This article provides an overview of the Windows 3.1 DDEML.DLL 
  68. and shows you how to use it in your programs. If you've used DDE 
  69. "in the raw" before, you'll have some 'unlearning' to do first. 
  70. If you've never programmed DDE before you're in for a treat: 
  71. with the DDEML, DDE has never been easier. We'll begin with a 
  72. brief overview of what DDE is and how it works. Then, I'll 
  73. present you with highlights of the DDE Management library, its 
  74. features and how it works. Finally, I'll conclude with some C++ 
  75. objects that utilize the DDEML. 
  76.  
  77. Keep in mind that Windows 3.1 was still in beta as this column 
  78. was written, and the only documentation on the DDEML is in an 
  79. on-line file. Further, version of Borland C++ that I'm using 
  80. works appears to work with Windows 3.1, but it doesn't do so 
  81. officially. The consequence of all this is: there's no absolute 
  82. guarantee that the code examples will work without modification 
  83. once Windows 3.1 officially arrives. Fortunately, you'll be able 
  84. to download an updated version of the code from WTJ at that 
  85. time.
  86.  
  87.  
  88. An Overview of Traditional DDE
  89.  
  90. As you're probably aware, DDE lets applications share data.  Like
  91. the Clipboard, DDE operations are often initiated by 
  92. the user via an application menu and can transfer data on a  one-
  93. time basis. But unlike the Clipboard, DDE often continues 
  94. without user interaction, and it can create on-going data 
  95. transfers.
  96.  
  97. In DDE terminology, the application that initiates the transfer 
  98. and requests the data is called the 'client' and the application 
  99. that provides the data is the 'server'. The client can 
  100. initiate a 'conversation' (i.e., establish a relationship) with 
  101. a client and request data from it.
  102.  
  103. DDE clients and servers 'converse' on specific contexts or 
  104. 'topics' from which smaller data 'items' are exchanged. A topic 
  105. is usually the name of a file or a document, a database table, a 
  106. worksheet or an application-specific string. An item is a 
  107. specific block of text, a database row(s), a cell(s) or another 
  108. application-specific string. A conversation is uniquely 
  109. identified by combining the server name and the topic name.  Data
  110. items are distinguished by the server, topic and item  names.
  111.  
  112. A DDE application can simultaneously engage in multiple 
  113. conversations with multiple DDE applications, on the same or on 
  114. multiple topics. An application can be a client in one 
  115. conversation and simultanelusly a server in another. A server 
  116. can supply data to more than one client at a time and a 
  117. client can request data from multiple servers at the same time. 
  118. Thus, 'AppA' can be a client of 'AppB' and 'AppC', and 
  119. simultaneously be a server to both 'AppB' and 'AppC'. The 
  120. only hitch is that you keep it all straight.
  121.  
  122. Traditionally speaking, DDE is a protocol: it establishes a set 
  123. of rules that determine what is supposed to happen, and when.  
  124. Unfortunately, some of these rules aren't terribly well 
  125. defined in the WinSDK (in fact, the DDEML docs are *not* 
  126. monuments to great clarity!), and consequently, it's been 
  127. difficult to figure out what a DDE application is supposed  to do
  128. in many circumstances. But the essence of a DDE 
  129. implementation is that a number of Windows messages are 
  130. provided, and these could be used to implement the protocol.
  131.  
  132. Since Windows doesn't offer any interprocess communication 
  133. features in the mature sense (i.e., shared memory, queues, 
  134. pipes, etc. as will be found in Windows NT), DDE applications 
  135. use messages (prefixed with 'WM_DDE_') to notify other 
  136. applications of their intentions, and then transfer data via the 
  137. global atom table. This table is a collection of strings that 
  138. any Windows application can register with the system, and which 
  139. any Windows application can access. Thus to transfer data, a 
  140. server application puts the data into the atom table, obtains a 
  141. handle to the data, includes the handle as a parameter with the 
  142. appropriate DDE message, and sends the latter to the client. The 
  143. receiving application processes the message and uses the 
  144. included handle to retrieve the data from the atom table.
  145.  
  146. Finally, since every DDE conversation takes place between two 
  147. windows (identified in the conversation by their window handles) 
  148. most Windows developers dedicate an invisible window to every 
  149. DDE conversation. This is particularly helpful if an application 
  150. engages in more than one conversation at a time. This requires 
  151. creating the new window and writing a window procedure that  can
  152. respond to the appropriate DDE messages. (Interestingly,  many
  153. Windows developers call these 'object windows' and did so  long
  154. before the Borland application framework of the same name 
  155. appeared.)
  156.  
  157.  
  158. The DDE Managment Library
  159.  
  160. Under the hood, the DDEML still uses the approach outlined 
  161. above: it communicates with DDE client and server applications 
  162. via the standard WM_DDE messages found in Windows. And, it 
  163. transfers data between those applications by using the global 
  164. atom table. For these reasons, an application that uses DDEML 
  165. will be completely compatible with DDE applications written 
  166. without the benefit of DDEML. 
  167.  
  168. However, DDEML applications needn't process WM_DDE_* messages or 
  169. access the atom table. Instead, the DDEML provides a set of API 
  170. functions, along with a few structures and special messages 
  171. known as 'transactions'. These facilities serve to hide the 
  172. details of traditional DDE conversations. So, your application 
  173. need only make a few function calls, and be designed to handle 
  174. the transaction messages when they are received.
  175.  
  176. While DDEML transactions are messages, they're not sent to your 
  177. application's window procedure. Instead, you must supply a 
  178. special function of your own that, like a window procedure, is 
  179. called by DDEML whenever your application receives a 
  180. transaction. This 'callback function' (or simply 'callback' for 
  181. short) must be capable of processing any transactions that your 
  182. application receives from DDEML. In addition, it must be 
  183. able to respond by posting transactions as replies and (if your 
  184. application is a DDE server) by supplying the appropriate data.
  185.  
  186. One of the nicer features of the DDEML is that you can filter 
  187. out unwanted and unneeded transactions, thus limiting the number 
  188. and types of transactions that have to be handled by your 
  189. application. These 'transaction filters' are completely dynamic: 
  190. you can change them on-the-fly -- even in the midst of a DDE 
  191. conversation. The consequence of this feature is that you can 
  192. optimize the performance of your DDE application: your callback 
  193. will never even receive the filtered messages, and the 
  194. sending application will receive a reply transaction indicating 
  195. that the transaction it sent wasn't processed (unless the 
  196. sending application itself filters out *these* transactions).
  197.  
  198. The DDEML lets you create DDE conversations that utilize both 
  199. synchronous *and* asynchronous transactions. And, the DDEML 
  200. supports all the traditional DDE conversational forms: Request, 
  201. Advise with notification (a.k.a., 'Warm Link'), Advise with Data 
  202. (a.k.a. 'Hot Link'), Execute and Poke. (In talks I've given at 
  203. SoftWare Development, I've had more than one student point out 
  204. the implicit sexual metaphor in the choice of these terms -- but 
  205. they're all legitimate, i.e., chosen by Microsoft.) In addition, 
  206. you can use the DDEML to create DDE Monitor, i.e., an 
  207. application that can monitor all the DDE conversations taking 
  208. place in the system. An example of just such a program is 
  209. DDESPY.EXE, which comes with the Windows 3.1 SDK.
  210.  
  211.  
  212. Initializing DDEML
  213.  
  214. The DDEML functions fall into several categories. For instance, 
  215. there are functions for initializing the connection between  your
  216. application and the DDEML, and for establishing (or 
  217. terminating) DDE conversations. There are transaction management 
  218. functions for initiating a transaction and abandoning 
  219. asynchronous ones. Other API functions let you create global 
  220. data objects, copy data to them and free them. The entire DDEML 
  221. API is summarized in the table in Figure 1.  You must #include 
  222. DDEML.H in the source files that reference them and link in 
  223. DDEML.LIB (DDEML.LIB is the import library for DDEML.DLL -- 
  224. which should be on the PATH when you run the application).  
  225.  
  226. Before it can call any other DDEML function, you must register 
  227. your application with the DDEML by calling DdeInitialize:
  228.  
  229. DWORD idInst = 0L; 
  230. FARPROC lpDdeProc;
  231. WORD errval;
  232. .
  233. .
  234. ..
  235. lpDdeProc = MakeProcInstance((FARPROC) DDECallback, hInst);
  236.  
  237. if(DdeInitialize((LPDWORD) &idInst,(PFNCALLBACK)lpDdeProc,
  238.      APPCMD_CLIENTONLY, 0L)) 
  239.      return FALSE;
  240.  
  241. DdeInitialize takes a pointer to a DWORD as its first parameter, 
  242. and sets this to an 'application-instance identifier' 
  243. -- essentially a handle that refers to that instance of the 
  244. task's usage of the DDEML (a task can have more than one 
  245. instance of DDEML). Without this handle, the DDEML wouldn't be 
  246. able to distinguish one usage of DDEML.DLL from another 
  247. by the same task, a problem if the application needs to use the 
  248. DDEML more than once at the same time. For instance, suppose an 
  249. application is using OLE (which uses DDE to link and embed 
  250. objects) while it's using the DDEML. The instance identifier 
  251. lets the DDEML keep the application's use of DDE in the OLE DLL 
  252. distinct from the application's own use of the DDEML.  
  253. Consequently, you have to retain the instance handle when 
  254. DdeInitialize returns, and pass it to a great number of the 
  255. DDEML API functions.
  256.  
  257. It's via DdeInitialize that you provide the address of your 
  258. application's callback (in the 2nd parameter). If your 
  259. application needs to establish DDE conversations with different 
  260. behaviors, you can call DdeInitialize for each type of 
  261. conversation, passing it a different callback function address, 
  262. and receiving a different instance handle each time. Each 
  263. instance handle is tied to the callback function passed with 
  264. each call to DdeInitialize.
  265.  
  266. You can also use DdeInitialize to set transaction filters (and 
  267. prevent the receipt of a variety of unwanted messages), as well 
  268. as specify whether the application is a DDE monitor and or needs 
  269. other services from the DDEML. 
  270.  
  271.  
  272. Server Naming Services
  273.  
  274. Once an application has registered itself with DDEML, it can 
  275. either initiate a conversation (if it's a DDE client) or 
  276. register its name and the topics it supports (if it's a DDE 
  277. server).
  278.  
  279. In traditional DDE, the name of a DDE server is based on the 
  280. application name. For example, the server name of EXCEL.EXE is 
  281. 'Excel'. To access Excel as a server with traditional DDE, you 
  282. have to specify that name (or rather an atom handle for it) in 
  283. the WM_DDE_INITIATE sent by your client application. (Of course, 
  284. a client application can always use wildcards invoke 
  285. reponses from *all* the available servers.) Plus, there's no way 
  286. to know if Excel is available without first attempting to 
  287. converse with it.
  288.  
  289. With DDEML, a server can take on more than one name: the 
  290. DdeNameService function lets a DDE server register multiple 
  291. names with the system. This function will send an XTYPE_REGISTER 
  292. transaction to every DDEML application in the system (except 
  293. those that are filtering out this transaction), thus notifying 
  294. them that the new server is available. One advantage is that 
  295. client applications won't have to attempt to converse with a 
  296. server to find out if it's available: they'll be notified once 
  297. it's available.
  298.  
  299. Another advantage is that the server can be more efficient: 
  300. DdeNameService can toggle the server's filtering of 
  301. XTYPE_CONNECT transactions (which are sent to every server when 
  302. a client is trying to start a conversation -- regardless of the 
  303. server name specified by the client). Thus, after calling 
  304. DdeNameService, the server will only receive XTYPE_CONNECT 
  305. transactions when a client specifies one of the registered 
  306. server names. A server can also use DdeNameService to 
  307. *de-register* a server name: since this causes DDEML to send the 
  308. XTYP_UNREGISTER transaction to every DDEML client, client 
  309. applications will know when a server is no longer available.
  310.  
  311. A server can also use DdeNameService to facilitate aliasing: the 
  312. server can use different names to indicate different types of 
  313. data as it becomes available.
  314.  
  315.  
  316. Creating A Conversation
  317.  
  318. A client application can establish a DDE conversation by calling 
  319. DdeConnect:
  320.  
  321. DWORD idInst;
  322. HSZ hszServer;
  323. HSZ hszTopic;
  324. HCONV hConv;
  325. .
  326. .
  327. .
  328. hConv = DdeConnect(idInst,hszServer, hszTopic,(LPVOID)NULL);
  329.  
  330. This function sends an XTYP_CONNECT transaction to the specified 
  331. server and takes four parameters: the instance handle returned 
  332. from DdeInitialize, and two string handles (one for the server 
  333. name and the other for the topic name). Either or both of the 
  334. string handles can be NULL: as with traditional DDE, a NULL 
  335. server name handle will allow a connection with any available 
  336. server, and a NULL topic handle will allow a conversation on any 
  337. topic supported by the selected server. 
  338.  
  339. The final parameter is a pointer to a CONVCONTEXT structure 
  340. (defined in DDEML.H). You can pass a NULL instead, and the 
  341. server will receive a default CONVCONTEXT structure along with 
  342. the XTYP_CONNECT transaction.  The string handles are created by 
  343. calling DdeCreateStringHandle.  
  344.  
  345. In traditional DDE, you have to create an invisible window 
  346. that's dedicated to managing the DDE conversation for you. If 
  347. DdeConnect finds a server with the specified name that supports 
  348. the specified topic, it will return a conversation handle:  in
  349. this implementation of DDEML, a handle to an invisible 
  350. window which DDEML creates for you. All messages (even WM_DDE_* 
  351. messages) sent to this window are handled by DDEML (it supplies 
  352. the window's window procedure) and then passed on to your 
  353. application's callback as DDEML transactions. Since there's no 
  354. guarantee that this approach will be used in future 
  355. implementations of DDEML, you can't rely on the conversation 
  356. handle to be a window handle. But you can still use the 
  357. conversation handle returned by DdeConnect to refer to a 
  358. particular conversation in other calls to the DDEML. And it's 
  359. vital for distinguishing between conversations if your 
  360. application is maintaining more than one DDEML conversation at a 
  361. time. 
  362.  
  363. Note that the DDEML also provides DdeConnectList, which lets a 
  364. client application establish more than one conversation at a 
  365. time. And, an application can terminate either single or 
  366. multiple conversations with DdeDisconnect or DdeDisconnectList, 
  367. respectively.
  368.  
  369. Once an conversation has been established between a client and  a
  370. server, the client application can call DdeClientTransaction  to
  371. receive data from the server:
  372.  
  373. HCONV hConv;
  374. HSZ hszItem;
  375. HDDEDATA hData;
  376. DWORD dwResult;
  377. .
  378. .
  379. .
  380. hData = DdeClientTransaction((LPBYTE)NULL,0,hConv,hszItem,
  381.      CF_TEXT,XTYP_ADVSTART,1000L,&dwResult);
  382.  
  383. This function lets a client application receive data on a 
  384. one-time basis (cold-link), receive notification of updates 
  385. (warm-link) or receive the updated data directly (hot-link). The 
  386. first two parameters are supplied in the event that the client 
  387. wants to pass data to the server (for the XTYP_EXECUTE or 
  388. XTYPE_POKE transactions), and specify a pointer to the data and 
  389. the data length. The third parameter is the conversation handle, 
  390. and the following one is a string handle that specifies the data 
  391. item being requested (like other string handles, created via 
  392. DdeCreateStringHandle). The fifth parameter specifies the 
  393. Clipboard data format being used (such as CF_TEXT).
  394.  
  395. The next parameter to DdeClientTransaction is the transaction 
  396. type, which can be: XTYP_ADVSTART (to initiate a warm or hot 
  397. link), XTYP_ADVSTOP (to terminate such a link), XTYP_EXECUTE (to 
  398. issue commands to the server), XTYP_POKE (to offer data to the 
  399. server) and XTYP_REQUEST (to make a one-time request for data). 
  400. The remaining two parameters specify a timeout value (the 
  401. maximum time in milliseconds that a client will wait for a 
  402. response from the server) and a result-flag variable. The bits 
  403. of the latter are set to designate the result of the 
  404. transaction.
  405. If it's successful, DdeClientTransaction returns a handle to a 
  406. global memory object that contains the data sent from the 
  407. server (provided the transaction was one that requested data). 
  408. Your application can pass this HDDEDATA handle to DdeGetData to 
  409. retrieve the data:
  410.  
  411. HDDEDATA hData;
  412. char szBuf[100];
  413. .
  414. .
  415. .
  416. DdeGetData(hData, (LPBYTE)szBuf, 100L, 0L);
  417.  
  418. In this example, DdeGetData will retrieve the data from a global 
  419. memory object (represented by hData) and place it in szBuf. The 
  420. last two parameters specify the number of bytes to copy from the 
  421. global object to the buffer and at what offset to begin copying. 
  422. If you don't know how big the data item is, you can pass a NULL 
  423. in place of the local buffer (in this case, szBuf), and 
  424. DdeGetData will return the size on bytes of the data item.
  425.  
  426. If this all sounds terribly complicated, it's not. The listings 
  427. accompanying this article show how to use these DDEML functions 
  428. in the context of a C++ object that makes a one-time data 
  429. request from a DDE server.
  430.  
  431.  
  432. CallBack Functions
  433.  
  434. While the examples above didn't necessitate a callback on the 
  435. client side, they don't preclude the client from having one and 
  436. the server certainly needed one. If you haven't already 
  437. guessed, callbacks are a *lot* like window procedures.  Both 
  438. consist of a large *switch* statement, process messages, and are 
  439. called by Windows, not your application. DDEML callbacks differ 
  440. from window procedures only in the number and types of their 
  441. arguments and the messages they process. Here, for example, is a 
  442. skeleton callback function:
  443.  
  444. HDDEDATA EXPENTRY DdeProc(WORD usType,WORD usFmt,HCONV hConv,    
  445.           HSZ hsz1,HSZ hsz2,HDDEDATA hData,DWORD lData1,DWORD
  446. lData2)             {
  447.      .
  448.      .
  449.      .
  450.      switch(usType)
  451.      {
  452.           .
  453.           .
  454.           .
  455.           }
  456.      }
  457.  
  458. As you can see, there are eight arguments passed to a callback 
  459. by the DDEML. (It's too bad that Microsoft didn't just supply a 
  460. pointer to a structure and pass the structure address to the 
  461. callback -- it would have been simpler, easier and faster). The 
  462. transaction message is the first parameter, followed by the data 
  463. format and the conversation handle. Whether the remaining 
  464. parameters (two string handles, a data handle and two 
  465. transaction-specific double-words) are used depends on the 
  466. transaction that's passed.
  467.  
  468.  
  469. Synchronous vs. Asynchronous Transactions
  470.  
  471. At this point, you should have a general idea of how DDEML 
  472. works. Before I present some C++ code examples for using it in 
  473. an application, there's on other feature of DDEML that's well 
  474. worth mentioning.
  475.  
  476. DDEML supports both asychronous and synchronous transactions. 
  477. The client can conform to the model of traditional DDE and issue 
  478. synchronous transactions where the client waits for the 
  479. transaction to be processed (as shown in the example using 
  480. DdeClientTransaction shown above). Or, it can issue an 
  481. asynchronous transactions: the client returns immediately 
  482. after calling DdeClientTransaction and proceeds normally; when 
  483. the server replies, the client's callback will receive the 
  484. response transaction. 
  485.  
  486. Synchronous transactions are simpler, faster and easier 
  487. to use. But they can hold up the client if the server takes a 
  488. long time to process a transaction or is processing a high 
  489. volume of transactions for this and other clients. Asynchronous 
  490. transactions, while more complex and difficult to maintain, 
  491. allow the client more control while a transaction is in 
  492. progress; indeed, a client and go on to issue other transactions 
  493. to the same or other servers.
  494.  
  495.  
  496. Using C++ with DDEML
  497.  
  498. To give you a 'taste' of what DDEML will be like (indeed, it 
  499. should be available and fully documented not long after this 
  500. article appears), I've written a few C++ classes that use it. 
  501. (Keep in mind that these won't be fully tested until after DDEML 
  502. is released, and that these classes should be considered 'works 
  503. in progress'. But they should give you a better idea of how 
  504. DDEML can be used from a C++ application.)
  505.  
  506. The two classes, DDE and DDEClient, can be found in DDE.H 
  507. and DDE.CPP. Class DDE contains all the basic facilities that 
  508. are needed by a DDE conversation, including the instance handle 
  509. and a pointer to the callback function (which an application 
  510. that uses the class has to provide). DDEClient relies on the DDE 
  511. class for these, and also provides member function to connect to 
  512. a DDE server and make a request for data. While I didn't have 
  513. time to implemenent member functions that can set up advise 
  514. loops (hot and warm links), it'd be simple enough to add them 
  515. using the facilities already built into DDEClient.
  516.  
  517. Both classes compile under Borland C++ 2.0 -- the latest 
  518. Borland compiler available at the time this column was 
  519. written. (Borland C++ 3.0 was still in Beta, and Microsoft C  7.0
  520. had not reached beta.) Also, both DDEClient and 
  521. the DDE class rely on some application classes that I wrote and 
  522. have been using over the past year. However, you can 
  523. easily reconfigure both of these classes to work with an 
  524. application class like Borland's ObjectWindows.
  525.  
  526. To demonstrate these DDE classes, I wrote a program, 
  527. DDECLNT.EXE, which uses DDEML to connect to any DDE 
  528. server and retrieve data from it. For example, to connect to 
  529. Excel and retrieve rows 1-3, columns 1-4 from ANNUAL.XLS (a 
  530. worksheet that comes with Excel):
  531.  
  532. o    Load Excel
  533. o    Use File|Load to load ANNUAL.XLS
  534. o    Load DDECLNT
  535. o    In DDECLNT's window (a single modal dialog), 
  536.      enter "Excel" in the 'Server Name' field, "ANNUAL.XLS" in 
  537.      the 'Topic Name' field, and "r1c1:r3c4" in the 'Item Name' 
  538.      field.
  539. o    Press the 'Request' button.
  540.  
  541. This will cause DDECLNT to create a DDEClient object,
  542.  
  543. DDEClient newClient(Bufs[0].buffer, Bufs[1].buffer, 
  544.    (FARPROC)DDEClientCallBack);
  545.  
  546. and pass it the server name and topic name as the first two 
  547. parameters, with the address of a callback function as the last 
  548. parameter.
  549.  
  550. To make a data request, DDECLNT.EXE only needs to call the 
  551. Request member function:
  552.  
  553. newClient.Request(Bufs[2].buffer);
  554.  
  555. Then, if a call to newClient.GetResult returns TRUE, the program 
  556. retrieves the data from newClient with a call to its GetData 
  557. member function,
  558.  
  559. SetDlgItemText(hDlg,RESULT_DATA,(LPSTR)newClient.GetData());
  560.  
  561. and puts the data directly into one of the data fields in the 
  562. main program's dialog.
  563.  
  564. The DDE and DDEClient classes do all the work.
  565.  
  566. I've tested the program under Win3.0 and Win3.1. I can't 
  567. guarantee it until Win3.1 and the final version of DDEML.DLL  are
  568. available, but you shouldn't have any problems with it. By  the
  569. way: considering how much DDECLNT.EXE does, I'm surprised  that
  570. it's only a little over 10k (of course, the 36k 
  571. DDEML.DLL has to be available, too).
  572.  
  573.  
  574. Conclusion
  575.  
  576. As you can see, the DDEML is a powerful addition to Windows 3.1, 
  577. but there's a lot to it. C++ certainly makes DDEML easier to 
  578. deal with. I'd like to re-visit DDE again in future columns, 
  579. once Win3.1 arrives and these classes have time to mature. But, 
  580. until next month, enjoy!
  581.  
  582. ===========================================
  583. [figure 1]
  584.  
  585. Session Management
  586. ------------------------
  587. DdeInitialize           Registers an application with the DDEML
  588. DdeUninitialize         Frees an application's DDEML resources
  589.  
  590. Conversation Management
  591. ------------------------
  592. DdeConnect              Establishes a conversation with a server
  593. DdeConnectList          Establishes multiple DDE conversations
  594. DdeDisconnect           Terminates a DDE conversation
  595. DdeDisconnectList       Destroys a DDE conversation list
  596. DdeEnableCallback       Enables or disables one or more DDE conversations
  597. DdePostAdvise           Prompts a server to send advise data to a client
  598. DdeQueryConvInfo        Retrieves information about a DDE conversation
  599. DdeQueryNextServer      Obtains the next handle in a conversation list
  600.  
  601. Transaction Management
  602. ------------------------
  603. DdeAbandonTransaction   Abandons an asynchronous transaction
  604. DdeClientTransaction    Begins a DDE data transaction
  605.  
  606. Data Management
  607. ------------------------
  608. DdeAccessData           Accesses a DDE global memory object
  609. DdeAddData              Adds data to a DDE global memory object
  610. DdeCreateDataHandle     Creates a DDE data handle
  611. DdeFreeDataHandle       Frees a global memory object
  612. DdeGetData              Copies data from a global memory object to a buffer
  613. DdeUnaccessData         Frees a DDE global memory object
  614.  
  615. String Management
  616. ------------------------
  617. DdeCmpStringHandles     Compares two DDE string handles
  618. DdeCreateStringHandle   Creates a DDE string handle
  619. DdeFreeStringHandle     Frees a DDE string handle
  620. DdeKeepStringHandle     Increments the use count for a string
  621. handle DdeQueryString   Copies string-handle text to a buffer
  622.  
  623. Miscellaneous Functions
  624. ------------------------
  625. DdeGetLastError         Returns the error code set by a DDEML function 
  626. DdeNameService          Registers or unregisters server name(s) 
  627. DdeSetUserHandle        Associates a user-defined handle with a transaction
  628.  
  629. =================================================================
  630.